热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

盘子|圆盘_递归与分治策略第一节:递归和典型递归问题

篇首语:本文由编程笔记#小编为大家整理,主要介绍了递归与分治策略-第一节:递归和典型递归问题相关的知识,希望对你有一定的参考价值。文章目录

篇首语:本文由编程笔记#小编为大家整理,主要介绍了递归与分治策略-第一节:递归和典型递归问题相关的知识,希望对你有一定的参考价值。



文章目录


  • 一:LeetCode中有关递归和分治的题目
  • 二:递归与分治概述
  • 三:递归基本概念
  • 四:典型递归问题分析
    • (1)阶乘
    • (2)Fibonacci数列
    • (3)排列问题
    • (4)整数划分
    • (5)汉诺塔

  • 五:递归算法原理
  • 六:递归算法优缺点


一:LeetCode中有关递归和分治的题目

递归题目:链接

分治题目:链接


二:递归与分治概述

递归与分治:任何可以用计算机求解的问题所需要的计算时间都和其规模有关,如果问题的规模越小,解题所需的计算时间往往也越短,从而也比较容易处理,例如


  • 对于




    n



    n


    n
    个元素的排序问题
    :当




    n


    =


    1



    n=1


    n=1
    时不需要任何计算;当




    n


    =


    2



    n=2


    n=2
    时,只需要做一次比较即可排好序;当




    n


    =


    3



    n=3


    n=3
    时则两次。而当




    n



    n


    n
    较大时,这个问题就不好处理了

所以要想直接解决一个较大的问题,有时是相当有困难的。所以分治法的设计思想是:将一个难以直接解决的大问题分割成一些规模较小的相同问题,以便各个击破,也即分而治之。如果原问题可以分割为




k



k


k
个子问题,




1


<


k





n



1

1<kn
&#xff0c;且这些子问题都可解&#xff0c;并可利用这些子问题的解求出原问题的解&#xff0c;那么这种分治法就是可行的。由分治法产生的子问题往往是原问题的较小模式&#xff0c;这为使用递归技术提供了方便。在这种情况下&#xff0c;反复应用分治手段&#xff0c;可以使子问题于原问题类型一致而其规模不断缩小&#xff0c;最终使子问题缩小到容易求出其解&#xff0c;由此自然引出递归算法。分治与递归像一对孪生兄弟&#xff0c;经常同时应用在算法设计中&#xff0c;并由此产生许多高效算法


三&#xff1a;递归基本概念

递归&#xff1a;递归算法是指直接或间接调用自身的算法&#xff1b;递归函数是指用函数自身给出定义的函数。在计算机算法设计与分析中&#xff0c;递归技术非常有用。使用递归技术往往可以使函数的定义和算法的描述简洁且易于理解&#xff0c;而且有些数据结构&#xff08;例如二叉树&#xff09;由于其本身具有递归特性&#xff0c;所以也特别使用递归的形式来求解

构造递归算法的基本步骤为


  • 确定递归边界
  • 描述递归关系
  • 构造递归函数

四&#xff1a;典型递归问题分析

&#xff08;1&#xff09;阶乘



阶乘&#xff1a;




n


!


&#61;


n


×


(


n





1


)


×


.


.


.


×


1



n!&#61;n×(n-1)×...×1


n!&#61;n×(n1)×...×1
&#xff0c;可递归定义如下



  • 第一个式子是递归边界&#xff0c;如果没有边界就有可能会引发无穷递归
  • 第二个式子是用较小自变量的函数来表示较大自变量的函数值&#xff0c;相当于问题规模减小

这个问题很简单&#xff0c;递归定义式很容易给出&#xff0c;所以编写代码如下

public class Factorial
public static int factorial_recurse(int n )
if(n &#61;&#61; 1)return 1;
return n * factorial_recurse(n-1);

public static void main(String[] args)
Scanner scanner &#61; new Scanner(System.in);
while(scanner.hasNextInt())
int n &#61; scanner.nextInt();
System.out.println(n&#43;"!等于&#xff1a;" &#43; factorial_recurse(n));



当然&#xff0c;递归解法也有其对应的非递归解法

public class Factorial
public static int factorial_none_recurse(int n)
int ret &#61; 1;
for(int i &#61; 1; i <&#61; n; i&#43;&#43;)
ret *&#61; i;

return ret;

public static void main(String[] args)
Scanner scanner &#61; new Scanner(System.in);
while(scanner.hasNextInt())
int n &#61; scanner.nextInt();
System.out.println(n&#43;"!等于&#xff1a;" &#43; factorial_none_recurse(n));




&#xff08;2&#xff09;Fibonacci数列



Fibonacci数列 &#xff1a;无穷数列【1 1 2 3 5 8 13 21 34 55…】称为Fibonacci数列&#xff0c;在Fibonacci数列中从第三个数字开始&#xff0c;每一个数字都是前两个数字之和&#xff0c;这是一个典型的递归问题&#xff0c;其递归定义式如下


很明显这个问题需要两个递归边界

问题还是比较简单&#xff0c;所以编写代码如下

public class Fibonacci
public static int fibonacci_recurse(int n)
if(n <&#61; 1)return 1;
return fibonacci_recurse(n-1) &#43; fibonacci_recurse(n-2);

public static void main(String[] args)
Scanner scanner &#61; new Scanner(System.in);
while(scanner.hasNextInt())
int n &#61; scanner.nextInt();
System.out.println("第" &#43; n&#43;"个fibonacci数为&#xff1a;" &#43; fibonacci_recurse(n));



fibonacci数列的纯递归写法有很多的重复子问题&#xff0c;所以其时间复杂度极高。所以对于其递归写法&#xff0c;我们可以进行一定优化&#xff0c;引入一个“备忘录”去解决这一部分重复子问题

public class Fibonacci
public static int fibonacci_recurse(int n)
if(n <&#61; 1)return 1;
return fibonacci_recurse(n-1) &#43; fibonacci_recurse(n-2);

public static int fibonacci_recurse_optimize(int[] memo, int n)
if(n <&#61; 1)return 1;
if(memo[n] !&#61; 0)return memo[n];//如果备忘录有这个元素那就不用递归了
memo[n] &#61; fibonacci_recurse(n-1) &#43; fibonacci_recurse(n-2);//保存下来
return memo[n];

public static void main(String[] args)
Scanner scanner &#61; new Scanner(System.in);
while(scanner.hasNextInt())
int n &#61; scanner.nextInt();
int[] memo &#61; new int[n&#43;1];//备忘录&#xff0c;元素如果为0表示没有被记录
long recurse_start_time &#61; System.nanoTime();
System.out.println("(递归)第" &#43; n&#43;"个fibonacci数为&#xff1a;" &#43; fibonacci_recurse_optimize(memo, n));
long recurse_end_time &#61; System.nanoTime();
long none_recurse_start_time &#61; System.nanoTime();
System.out.println("(非递归)第" &#43; n&#43;"个fibonacci数为&#xff1a;" &#43; fibonacci_recurse_optimize(memo, n));
long none_recurse_end_time &#61; System.nanoTime();
System.out.println("递归用时&#xff1a;" &#43; (recurse_end_time - recurse_start_time));
System.out.println("非递归用时&#xff1a;" &#43; (none_recurse_end_time - none_recurse_start_time));
System.out.println("----------------------------------------------------");

scanner.close();



&#xff08;3&#xff09;排列问题



排列问题&#xff1a;设计一个递归算法生成




n



n


n
个元素的全排列


采用递归解法&#xff0c;可以将规模为




n



n


n
的全排列问题转换为规模为




n





1



n-1


n1
的全排列问题。所以他可以看作是固定




[


0


,


k


]



[0, k]


[0,k]
位&#xff0c;对




[


k


&#43;


1


,


n


]



[k&#43;1, n]


[k&#43;1,n]
位进行全排列&#xff0c;当




k


&#43;


1


&#61;


n



k&#43;1&#61;n


k&#43;1&#61;n
时&#xff0c;递归结束

如下为决策树&#xff0c;每一个子决策树都是一个全排列问题&#xff0c;


代码如下

public class Permutations
public static void swap(int[] test, int k, int i)
int temp &#61; test[k];
test[k] &#61; test[i];
test[i] &#61; temp;

public static void perm(int[] list, int k, int m)
//只有一个元素&#xff0c;递归结束&#xff0c;这一种排列情况可以输出了
if(k &#61;&#61; m)
for(int i &#61; 0; i <&#61; m; i&#43;&#43;)
System.out.print(list[i]);

System.out.println();

//否则开始递归
else
for(int i &#61; k; i <&#61; m; i&#43;&#43;)
swap(list, k, i);
perm(list, k&#43;1, m);
swap(list, k, i);



public static void main(String[] args)
int[] test &#61; new int[]2, 3, 5, 7;
perm(test, 0, 3);



&#xff08;4&#xff09;整数划分



整数划分&#xff1a;将正整数




n



n


n
表示成一系列整数之和&#xff0c;即




n


&#61;



n


1



&#43;



n


2



&#43;


.


.


.


&#43;



n


k




n&#61;n_1&#43;n_2&#43;...&#43;n_k


n&#61;n1&#43;n2&#43;...&#43;nk


在这里&#xff0c;正整数




n



n


n
的这种表示称为正整数




n



n


n
的划分。正整数




n



n


n
的不同划分个数称为正整数




n



n


n
的划分数&#xff0c;记为




p


(


n


)



p(n)


p(n)
&#xff0c;例如6有如下11种不同的划分方法&#xff0c;所以




p


(


6


)


&#61;


11



p(6)&#61;11


p(6)&#61;11

6
5&#43;1
4&#43;2, 4&#43;1&#43;1
3&#43;3, 3&#43;2&#43;1, 3&#43;1&#43;1&#43;1
2&#43;2&#43;2, 2&#43;2&#43;1&#43;1, 2&#43;1&#43;1&#43;1&#43;1
1&#43;1&#43;1&#43;1&#43;1&#43;1

在正整数




n



n


n
的所有划分中&#xff0c;用




q


(


n


,


m


)



q(n,m)


q(n,m)
表示最大加数





n


1




n_1


n1
不大于




m



m


m
的划分个数&#xff0c;正整数




n



n


n
的划分数




p


(


n


)


&#61;


q


(


n


,


n


)



p(n)&#61;q(n,n)


p(n)&#61;q(n,n)







  • n





    1



    n\\geq1


    n1
    时&#xff0c;




    q


    (


    n


    ,


    1


    )


    &#61;


    1



    q(n,1)&#61;1


    q(n,1)&#61;1
    &#xff0c;因为当最大加数最大为1时&#xff0c;任何正整数只有一种划分形式&#xff0c;也即全1
  • 由于最大加数不可能大于




    n



    n


    n
    &#xff0c;所以当




    m





    n



    m \\geq n


    mn
    时&#xff0c;




    q


    (


    n


    ,


    m


    )


    &#61;


    q


    (


    n


    ,


    n


    )



    q(n,m)&#61;q(n,n)


    q(n,m)&#61;q(n,n)
    ,




    q


    (


    1


    ,


    m


    )


    &#61;


    1



    q(1,m)&#61;1


    q(1,m)&#61;1
  • 正整数




    n



    n


    n
    的划分由





    n


    1



    &#61;


    n



    n_1&#61;n


    n1&#61; var cpro_id = "u6885494";
推荐阅读
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • 1:有如下一段程序:packagea.b.c;publicclassTest{privatestaticinti0;publicintgetNext(){return ... [详细]
  • 本文介绍了Java并发库中的阻塞队列(BlockingQueue)及其典型应用场景。通过具体实例,展示了如何利用LinkedBlockingQueue实现线程间高效、安全的数据传递,并结合线程池和原子类优化性能。 ... [详细]
  • 深入解析Android自定义View面试题
    本文探讨了Android Launcher开发中自定义View的重要性,并通过一道经典的面试题,帮助开发者更好地理解自定义View的实现细节。文章不仅涵盖了基础知识,还提供了实际操作建议。 ... [详细]
  • Explore how Matterverse is redefining the metaverse experience, creating immersive and meaningful virtual environments that foster genuine connections and economic opportunities. ... [详细]
  • Explore a common issue encountered when implementing an OAuth 1.0a API, specifically the inability to encode null objects and how to resolve it. ... [详细]
  • 本文详细探讨了Java中的24种设计模式及其应用,并介绍了七大面向对象设计原则。通过创建型、结构型和行为型模式的分类,帮助开发者更好地理解和应用这些模式,提升代码质量和可维护性。 ... [详细]
  • 本文基于刘洪波老师的《英文词根词缀精讲》,深入探讨了多个重要词根词缀的起源及其相关词汇,帮助读者更好地理解和记忆英语单词。 ... [详细]
  • 深入解析JVM垃圾收集器
    本文基于《深入理解Java虚拟机:JVM高级特性与最佳实践》第二版,详细探讨了JVM中不同类型的垃圾收集器及其工作原理。通过介绍各种垃圾收集器的特性和应用场景,帮助读者更好地理解和优化JVM内存管理。 ... [详细]
  • Python 异步编程:深入理解 asyncio 库(上)
    本文介绍了 Python 3.4 版本引入的标准库 asyncio,该库为异步 IO 提供了强大的支持。我们将探讨为什么需要 asyncio,以及它如何简化并发编程的复杂性,并详细介绍其核心概念和使用方法。 ... [详细]
  • 本文将介绍如何编写一些有趣的VBScript脚本,这些脚本可以在朋友之间进行无害的恶作剧。通过简单的代码示例,帮助您了解VBScript的基本语法和功能。 ... [详细]
  • 技术分享:从动态网站提取站点密钥的解决方案
    本文探讨了如何从动态网站中提取站点密钥,特别是针对验证码(reCAPTCHA)的处理方法。通过结合Selenium和requests库,提供了详细的代码示例和优化建议。 ... [详细]
  • Java 中的 BigDecimal pow()方法,示例 ... [详细]
  • 本文介绍如何利用动态规划算法解决经典的0-1背包问题。通过具体实例和代码实现,详细解释了在给定容量的背包中选择若干物品以最大化总价值的过程。 ... [详细]
  • 在前两篇文章中,我们探讨了 ControllerDescriptor 和 ActionDescriptor 这两个描述对象,分别对应控制器和操作方法。本文将基于 MVC3 源码进一步分析 ParameterDescriptor,即用于描述 Action 方法参数的对象,并详细介绍其工作原理。 ... [详细]
author-avatar
打完BOSS好睡觉1998
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有